(function(win) {
	GSCloudPlugin = {
		Printers: [],
		webskt: null,
		serialPortWebskt: null,
		version: "",
		appKey: "",
		company: "",
		webSocketAddress: "ws://127.0.0.1:8366",
		httpAddress: "http://127.0.0.1:8365",
		connectionErrorMessage: "无法连接到光速云插件服务，请确认光速云插件是否已启动",
		FAILURE: 0,
		SUCCESS: 1,
		PENDING: 2,
		OnError: null,
		OnSuccess: null,
		OnMessage: function(r) {
			var result = eval("(" + r + ")");
			if (!result.Status) {
				var message = result.Message;
				if (GSCloudPlugin.OnError !== null && typeof(GSCloudPlugin.OnError) === "function") {
					GSCloudPlugin.OnError(message, result.Code, result.Title, result.OperationType)
				} else {
					console.log(message)
				}
			} else {
				if (GSCloudPlugin.OnSuccess !== null && typeof(GSCloudPlugin.OnSuccess) === "function") {
					GSCloudPlugin.OnSuccess(result)
				}
			}
		},
		CanUseWebSocket: function() {
			var userAgent = navigator.userAgent;
			if (userAgent.indexOf("Chrome") > -1) {
				return true
			}
			if (window.location.protocol.indexOf("https") > -1) {
				return false
			}
			if (userAgent.indexOf("MSIE") > -1) {
				var version = userAgent.match(/MSIE ([\d.]+)/)[1];
				if (version < 10) {
					return false
				}
			}
			return true
		},
		OpenWebSocket: function(openCallback) {
			this.webskt = new WebSocket(this.webSocketAddress + "/print");
			this.webskt.onopen = openCallback;
			this.webskt.onerror = function() {
				var message = "无法连接到光速云插件服务，请确认光速云插件是否已启动";
				if (GSCloudPlugin.OnError !== null && typeof(GSCloudPlugin.OnError) === "function") {
					GSCloudPlugin.OnError(message)
				} else {
					console.log(message)
				}
			};
			this.webskt.onmessage = function(r) {
				GSCloudPlugin.OnMessage(r.data)
			}
		},
		SetAppKey: function(appKey, company) {
			this.appKey = appKey;
			this.company = company
		},
		SetParams: function(obj1, obj2) {
			if (typeof(obj2) === "object") {
				for (var k in obj1) {
					obj2[k] = obj1[k]
				}
				return obj2
			} else {
				return obj1
			}
		},
		PrintPdf: function(data) {
			var requestData = this.SetParams({
				MediumType: "Pdf",
				AppKey: this.appKey,
				Company: this.company,
				OperationType: "Print"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		PrintImage: function(data) {
			var requestData = this.SetParams({
				MediumType: "Image",
				AppKey: this.appKey,
				Company: this.company,
				OperationType: "Print"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		PrintHtml: function(data) {
			var requestData = this.SetParams({
				MediumType: "Html",
				AppKey: this.appKey,
				Company: this.company,
				OperationType: "Print"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		PrintDraw: function(data) {
			var requestData = this.SetParams({
				MediumType: "Draw",
				AppKey: this.appKey,
				Company: this.company,
				OperationType: "Print"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		PrintWord: function(data) {
			var requestData = this.SetParams({
				MediumType: "Word",
				AppKey: this.appKey,
				Company: this.company,
				OperationType: "Print"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		PrintExcel: function(data) {
			var requestData = this.SetParams({
				MediumType: "Excel",
				AppKey: this.appKey,
				Company: this.company,
				OperationType: "Print"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		PrintPpt: function(data) {
			var requestData = this.SetParams({
				MediumType: "Ppt",
				AppKey: this.appKey,
				Company: this.company,
				OperationType: "Print"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		PrintCommand: function(data) {
			var requestData = this.SetParams({
				MediumType: "Command",
				AppKey: this.appKey,
				Company: this.company,
				OperationType: "Print"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		DownloadFile: function(data) {
			var requestData = this.SetParams({
				OperationType: "DownloadFile"
			}, data);
			this.Send(requestData, data.OnError, data.OnSuccess)
		},
		DownloadFileAsync: function(data) {
			var requestData = this.SetParams({
				OperationType: "DownloadFileAsync"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		GetPrinterStatus: function(data) {
			var requestData = this.SetParams({
				OperationType: "GetPrinterStatus"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		GetPrinterInfo: function(data) {
			var requestData = this.SetParams({
				OperationType: "GetPrinterInfo"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		GetPrinters: function(data) {
			var requestData = this.SetParams({
				OperationType: "GetPrinters"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		GetPrintQueue: function(data) {
			var requestData = this.SetParams({
				OperationType: "GetPrintQueue"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		GetPrinterPaperSources: function(data) {
			var requestData = this.SetParams({
				OperationType: "GetPrinterPaperSources"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		GetNetworkAdapterMacAddress: function(data) {
			var requestData = this.SetParams({
				OperationType: "GetNetworkAdapterMacAddress"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		Speak: function(data) {
			var requestData = this.SetParams({
				OperationType: "Speak"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		GetFilePageCount: function(data) {
			var requestData = this.SetParams({
				OperationType: "GetFilePageCount"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		BlockPrintJob: function(data) {
			var requestData = this.SetParams({
				OperationType: "BlockPrintJob"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		RestartPrintJob: function(data) {
			var requestData = this.SetParams({
				OperationType: "RestartPrintJob"
			}, data);
			this.Send(requestData, data.OnSuccess, data.OnError)
		},
		Send: function(data, successCallback, errorCallback) {
			data.Origin = window.location.origin;
			var currentObj = this;
			if (this.CanUseWebSocket()) {
				if (this.webskt === null || this.webskt.readyState === WebSocket.CLOSING || this.webskt.readyState === WebSocket.CLOSED) {
					this.webskt = new WebSocket(this.webSocketAddress + "/print");
					this.webskt.messageChannels = {};
					this.webskt.onmessage = function(event) {
						var result = eval("(" + event.data + ")");
						var mc = this.messageChannels[result.ResponseId];
						delete result["ResponseId"];
						if (!result.Status) {
							if (mc.ErrorCallback !== null && typeof(mc.ErrorCallback) === "function") {
								mc.ErrorCallback(result)
							} else {
								if (GSCloudPlugin.OnError !== null && typeof(GSCloudPlugin.OnError) === "function") {
									GSCloudPlugin.OnError(result.Message, result.Code, result.Title, result.OperationType)
								} else {
									console.log(result)
								}
							}
						} else {
							if (mc.SuccessCallback !== null && typeof(mc.SuccessCallback) === "function") {
								mc.SuccessCallback(result)
							} else {
								if (GSCloudPlugin.OnSuccess !== null && typeof(GSCloudPlugin.OnSuccess) === "function") {
									GSCloudPlugin.OnSuccess(result)
								}
							}
						} if (result.Status !== currentObj.PENDING) {
							delete this.messageChannels[result.ResponseId]
						}
					}
				}
				this.GenerateMessageChannel(this.webskt, data, successCallback, errorCallback);
				this.webskt.onerror = function() {
					if (GSCloudPlugin.OnError !== null && typeof(GSCloudPlugin.OnError) === "function") {
						GSCloudPlugin.OnError(currentObj.connectionErrorMessage)
					} else {
						for (var key in currentObj.webskt.messageChannels) {
							var mc = currentObj.webskt.messageChannels[key];
							var result = {
								Status: currentObj.FAILURE,
								Code: 50011,
								Message: currentObj.connectionErrorMessage,
								Title: mc.Data.Title
							};
							if (mc.ErrorCallback !== null && typeof(mc.ErrorCallback) === "function") {
								mc.ErrorCallback(result)
							} else {
								console.log(result)
							}
							delete currentObj.webskt.messageChannels[key]
						}
					}
				};
				if (this.webskt.readyState === WebSocket.CONNECTING) {
					this.webskt.addEventListener("open", function() {
						this.send(JSON.stringify(data))
					})
				} else {
					currentObj.webskt.send(JSON.stringify(data))
				}
			} else {
				var xhr = new XMLHttpRequest();
				xhr.onreadystatechange = function() {
					if (xhr.readyState === 4) {
						var result;
						if (xhr.status === 200) {
							result = eval("(" + xhr.responseText + ")");
							delete result["ResponseId"];
							if (!result.Status) {
								if (errorCallback !== null && typeof(errorCallback) === "function") {
									errorCallback(result)
								} else {
									if (GSCloudPlugin.OnError !== null && typeof(GSCloudPlugin.OnError) === "function") {
										GSCloudPlugin.OnError(result.Message, result.Code, result.Title, result.OperationType)
									} else {
										console.log(result)
									}
								}
							} else {
								if (successCallback !== null && typeof(successCallback) === "function") {
									successCallback(result)
								} else {
									if (GSCloudPlugin.OnSuccess !== null && typeof(GSCloudPlugin.OnSuccess) === "function") {
										GSCloudPlugin.OnSuccess(result)
									}
								}
							}
						} else {
							result = {
								Status: currentObj.FAILURE,
								Code: 50011,
								Message: currentObj.connectionErrorMessage,
								Title: data.Title
							};
							if (errorCallback !== null && typeof(errorCallback) === "function") {
								errorCallback(result)
							} else {
								if (GSCloudPlugin.OnError !== null && typeof(GSCloudPlugin.OnError) === "function") {
									GSCloudPlugin.OnError(currentObj.connectionErrorMessage)
								} else {
									console.log(result)
								}
							}
						}
					}
				};
				xhr.open("POST", this.httpAddress + "/print", true);
				data.IsAsync = false;
				xhr.send(JSON.stringify(data))
			}
		},
		SendSerialPort: function(data, successCallback, errorCallback) {
			data.Origin = window.location.origin;
			var currentObj = this;
			if (this.CanUseWebSocket()) {
				if (this.serialPortWebskt === null || this.serialPortWebskt.readyState === WebSocket.CLOSING || this.serialPortWebskt.readyState === WebSocket.CLOSED) {
					this.serialPortWebskt = new WebSocket(this.webSocketAddress + "/serialPort");
					this.serialPortWebskt.messageChannels = {};
					this.serialPortWebskt.onmessage = function(event) {
						var result = eval("(" + event.data + ")");
						var mc = this.messageChannels[result.ResponseId];
						delete result["ResponseId"];
						if (!result.Status) {
							if (mc.ErrorCallback !== null && typeof(mc.ErrorCallback) === "function") {
								mc.ErrorCallback(result)
							} else {
								console.log(result)
							}
						} else {
							if (mc.SuccessCallback !== null && typeof(mc.SuccessCallback) === "function") {
								mc.SuccessCallback(result)
							}
						} if (result.Status !== currentObj.PENDING) {
							delete this.messageChannels[result.ResponseId]
						}
					}
				}
				var requestId = data.PortName + data.OperationType;
				data.RequestId = requestId;
				var mssageChannel = {
					SuccessCallback: successCallback,
					ErrorCallback: errorCallback,
					Data: data
				};
				for (var key in this.serialPortWebskt.messageChannels) {
					if (key === requestId) {
						this.serialPortWebskt.messageChannels[key] = mssageChannel
					}
				}
				if (!this.serialPortWebskt.messageChannels[requestId]) {
					this.serialPortWebskt.messageChannels[requestId] = mssageChannel
				}
				this.serialPortWebskt.onerror = function() {
					for (var key in currentObj.serialPortWebskt.messageChannels) {
						var mc = currentObj.serialPortWebskt.messageChannels[key];
						var result = {
							Status: currentObj.FAILURE,
							Code: 50011,
							Message: currentObj.connectionErrorMessage
						};
						if (mc.ErrorCallback !== null && typeof(mc.ErrorCallback) === "function") {
							mc.ErrorCallback(result)
						} else {
							console.log(result)
						}
						delete currentObj.serialPortWebskt.messageChannels[key]
					}
				};
				if (this.serialPortWebskt.readyState === WebSocket.CONNECTING) {
					this.serialPortWebskt.addEventListener("open", function() {
						this.send(JSON.stringify(data))
					})
				} else {
					currentObj.serialPortWebskt.send(JSON.stringify(data))
				}
			} else {
				ajax(data);

				function ajax(data) {
					var strData = JSON.stringify(data);
					var xhr = new XMLHttpRequest();
					xhr.onreadystatechange = function() {
						if (xhr.readyState === 4) {
							if (xhr.status === 200) {
								var result = eval("(" + xhr.responseText + ")");
								delete result["ResponseId"];
								if (!result.Status) {
									if (errorCallback !== null && typeof(errorCallback) === "function") {
										errorCallback(result)
									} else {
										console.log(result)
									}
								} else {
									if (successCallback !== null && typeof(successCallback) === "function") {
										successCallback(result)
									}
								} if (typeof(data.KeepAlive) !== "undefined" && data.KeepAlive) {
									ajax(data)
								}
							} else {
								console.log(xhr)
							}
						}
					};
					xhr.open("POST", currentObj.httpAddress + "/serialPort", true);
					xhr.send(strData)
				}
			}
		},
		ReadSerialPortData: function(data) {
			var requestData = this.SetParams({
				AppKey: this.appKey,
				Company: this.company,
				OperationType: "SerialPortRead"
			}, data);
			this.SendSerialPort(requestData, data.OnSuccess, data.OnError)
		},
		WriteLineSerialPortData: function(data) {
			var requestData = this.SetParams({
				AppKey: this.appKey,
				Company: this.company,
				OperationType: "SerialPortWriteLine"
			}, data);
			this.SendSerialPort(requestData, data.OnSuccess, data.OnError)
		},
		WriteSerialPortData: function(data) {
			var requestData = this.SetParams({
				AppKey: this.appKey,
				Company: this.company,
				Company: this.company,
				OperationType: "SerialPortWrite"
			}, data);
			this.SendSerialPort(requestData, data.OnSuccess, data.OnError)
		},
		WriteBytesSerialPortData: function(data) {
			var requestData = this.SetParams({
				AppKey: this.appKey,
				Company: this.company,
				OperationType: "SerialPortWrite"
			}, data);
			this.SendSerialPort(requestData, data.OnSuccess, data.OnError)
		},
		CloseSerialPort: function(data) {
			var requestData = this.SetParams({
				OperationType: "SerialPortClose"
			}, data);
			this.SendSerialPort(requestData, data.OnSuccess, data.OnError)
		},
		GetWeight: function(data) {
			var requestData = this.SetParams({
				AppKey: this.appKey,
				Company: this.company,
				OperationType: "GetWeight"
			}, data);
			this.SendSerialPort(requestData, data.OnSuccess, data.OnError)
		},
		CloseWeighingPort: function(data) {
			var requestData = {
				OperationType: "CloseWeighingPort"
			};
			this.SendSerialPort(requestData, data.OnSuccess, data.OnError)
		},
		GenerateMessageChannel: function(websocket, data, successCallback, errorCallback) {
			var requestId = Date.now().toString(36) + Math.random().toString(36).substr(3) + Math.random().toString(36).substr(3) + Math.random().toString(36).substr(3);
			if (websocket.messageChannels[requestId] === undefined) {
				websocket.messageChannels[requestId] = {
					SuccessCallback: successCallback,
					ErrorCallback: errorCallback,
					Data: data
				};
				data.RequestId = requestId
			} else {
				this.GenerateMessageChannel(data, successCallback, errorCallback)
			}
		}
	};
	win.GSCloudPlugin = GSCloudPlugin;
	if (typeof win.JSON === "undefined") {
		win.JSON = {}
	}
	if (typeof win.JSON.stringify !== "function") {
		meta = {
			"\b": "\\b",
			"\t": "\\t",
			"\n": "\\n",
			"\f": "\\f",
			"\r": "\\r",
			'"': '\\"',
			"\\": "\\\\"
		};
		win.JSON.stringify = function(value, replacer, space) {
			var i;
			gap = "";
			indent = "";
			if (typeof space === "number") {
				for (i = 0; i < space; i += 1) {
					indent += " "
				}
			} else {
				if (typeof space === "string") {
					indent = space
				}
			}
			rep = replacer;
			if (replacer && typeof replacer !== "function" && (typeof replacer !== "object" || typeof replacer.length !== "number")) {
				throw new Error("JSON.stringify")
			}
			return str("", {
				"": value
			})
		};

		function str(key, holder) {
			var i;
			var k;
			var v;
			var length;
			var mind = gap;
			var partial;
			var value = holder[key];
			if (value && typeof value === "object" && typeof value.toJSON === "function") {
				value = value.toJSON(key)
			}
			if (typeof rep === "function") {
				value = rep.call(holder, key, value)
			}
			switch (typeof value) {
				case "string":
					return quote(value);
				case "number":
					return (isFinite(value)) ? String(value) : "null";
				case "boolean":
				case "null":
					return String(value);
				case "object":
					if (!value) {
						return "null"
					}
					gap += indent;
					partial = [];
					if (Object.prototype.toString.apply(value) === "[object Array]") {
						length = value.length;
						for (i = 0; i < length; i += 1) {
							partial[i] = str(i, value) || "null"
						}
						v = partial.length === 0 ? "[]" : gap ? ("[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]") : "[" + partial.join(",") + "]";
						gap = mind;
						return v
					}
					if (rep && typeof rep === "object") {
						length = rep.length;
						for (i = 0; i < length; i += 1) {
							if (typeof rep[i] === "string") {
								k = rep[i];
								v = str(k, value);
								if (v) {
									partial.push(quote(k) + ((gap) ? ": " : ":") + v)
								}
							}
						}
					} else {
						for (k in value) {
							if (Object.prototype.hasOwnProperty.call(value, k)) {
								v = str(k, value);
								if (v) {
									partial.push(quote(k) + ((gap) ? ": " : ":") + v)
								}
							}
						}
					}
					v = partial.length === 0 ? "{}" : gap ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" : "{" + partial.join(",") + "}";
					gap = mind;
					return v
			}
		}
		var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;

		function quote(string) {
			rx_escapable.lastIndex = 0;
			return rx_escapable.test(string) ? '"' + string.replace(rx_escapable, function(a) {
				var c = meta[a];
				return typeof c === "string" ? c : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4)
			}) + '"' : '"' + string + '"'
		}
	}
})(window);
